Skip to content

Comments

feat: ability to customize categories#1328

Merged
MrgSub merged 16 commits intoMail-0:stagingfrom
retrogtx:customize-categories
Jun 18, 2025
Merged

feat: ability to customize categories#1328
MrgSub merged 16 commits intoMail-0:stagingfrom
retrogtx:customize-categories

Conversation

@retrogtx
Copy link
Contributor

@retrogtx retrogtx commented Jun 17, 2025

Description

Users can now customize the mail categories (All, Important, Personal, Promotions) by modifying:

  • the search value used when the category is selected using th input fields
  • add a name of the category, you can even update the default values for something like "all mails" now
  • the order in which it appears
  • whether it's the default category or not

Things remaining to make it better:

  • should be able to update order of category by just doing a simple drag and drop (next pr)
  • integrate llms, need to figure out where should I put the input fields on the /settings/categories page, prolly besides the raw input field (update: done)

Blocking issue:

  • fix the same order of allotment issue ( this is the blocking part, can merge after this) (update: done)

The button should be at the top right, since the user needs to scroll way at the bottom. However rest of the app so far has the save button at the bottom of the screen so this follows the same thing. Should change for this page upon discussion ofc.


Type of Change

Please delete options that are not relevant.

  • ✨ New feature (non-breaking change which adds functionality)

Areas Affected

Please check all that apply:

  • Email Integration (Gmail, IMAP, etc.)
  • User Interface/Experience
  • Authentication/Authorization
  • Data Storage/Management
  • API Endpoints
  • Documentation
  • Testing Infrastructure
  • Development Workflow
  • Deployment/Infrastructure

Testing Done

Describe the tests you've done:

  • Unit tests added/updated
  • Integration tests added/updated
  • Manual testing performed
  • Cross-browser testing (if UI changes)
  • Mobile responsiveness verified (if UI changes)

Security Considerations

For changes involving data or authentication:

  • No sensitive data is exposed
  • Authentication checks are in place
  • Input validation is implemented
  • Rate limiting is considered (if applicable)

Checklist

  • I have read the CONTRIBUTING document
  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have commented my code, particularly in complex areas
  • I have updated the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix/feature works
  • All tests pass locally
  • Any dependent changes are merged and published

Screenshots/Recordings

Add screenshots or recordings here if applicable.

updated version:

https://x.com/amritwt/status/1935183583033049268


By submitting this pull request, I confirm that my contribution is made under the terms of the project's license.

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features
    • Introduced a settings page for managing mail categories, allowing users to edit category names, queries, order, and default status.
    • Added the ability to set a default mail category, with changes saved asynchronously and feedback provided via notifications.
    • Navigation menu now includes a "Categories" item under Settings.
  • Improvements
    • Mail categories and default selections are now dynamically loaded from user settings instead of being hardcoded.
    • Shortcut labels and mail list hotkeys updated to reflect dynamic category names.
  • Localization
    • Added English translation for the new "Categories" settings navigation item.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jun 17, 2025

Walkthrough

This update introduces dynamic category management to the mail application. It adds a categories settings page, a hook for merging user and default categories, updates schemas to support category settings, and refactors mail components to use dynamic category data. Navigation and localization are updated to include the new categories feature.

Changes

File(s) Change Summary
apps/mail/app/(routes)/settings/categories/page.tsx Added a new React component for the categories settings page, allowing users to view, edit, and save mail category settings dynamically.
apps/mail/app/routes.ts Added a new route for /settings/categories pointing to the new categories settings page.
apps/mail/components/mail/mail.tsx Refactored mail components to use dynamic category data from hooks instead of static arrays; updated mutation calls and query state defaults to use dynamic category IDs.
apps/mail/config/navigation.ts Inserted a new navigation item for "Categories" under settings, linking to the new settings page.
apps/mail/hooks/use-categories.ts Introduced a new hook to merge user and default categories and provide a default category ID.
apps/mail/locales/en.json Added a new localization string for the "Categories" navigation item.
apps/server/src/lib/schemas.ts Defined a new mail category schema, updated user settings schema to support categories, and provided default mail categories.
apps/server/src/lib/prompts.ts Extended Gmail and Outlook search assistant system prompts with a guideline for exact-match category name queries to produce specific search queries verbatim.
apps/server/src/trpc/index.ts Added categoriesRouter to the main appRouter.
apps/server/src/trpc/routes/categories.ts Created a new categoriesRouter with a public procedure defaults returning default mail categories.
apps/mail/app/(routes)/settings/shortcuts/page.tsx Updated shortcut labels to dynamically include category names based on category settings.
apps/mail/lib/hotkeys/mail-list-hotkeys.tsx Added a callback to switch mail categories by index and refactored existing handlers to use this function.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CategoriesSettingsPage
    participant useCategorySettings
    participant Server

    User->>CategoriesSettingsPage: Visit /settings/categories
    CategoriesSettingsPage->>useCategorySettings: Fetch and merge categories
    useCategorySettings->>Server: Get user settings (categories)
    useCategorySettings-->>CategoriesSettingsPage: Return merged categories
    User->>CategoriesSettingsPage: Edit and Save categories
    CategoriesSettingsPage->>Server: Save updated categories
    Server-->>CategoriesSettingsPage: Confirm save
    CategoriesSettingsPage-->>User: Show success/failure notification
Loading

Suggested labels

design

Suggested reviewers

  • MrgSub

Poem

A bunny hopped through code today,
Bringing categories to light in a brand new way.
Dynamic lists that shift and grow,
With hooks and schemas in tow!
Navigation’s clearer, settings are neat—
This mail app’s update is quite a treat!
🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c5ddf1c and 9cd4a75.

📒 Files selected for processing (3)
  • apps/mail/components/mail/mail.tsx (7 hunks)
  • apps/mail/locales/en.json (1 hunks)
  • apps/server/src/trpc/index.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/server/src/trpc/index.ts
  • apps/mail/locales/en.json
  • apps/mail/components/mail/mail.tsx
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@retrogtx retrogtx marked this pull request as draft June 17, 2025 15:06
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🔭 Outside diff range comments (1)
apps/mail/components/mail/mail.tsx (1)

189-208: toast.promise options: finally is not a recognised key

sonner’s toast.promise accepts loading, success, and error.
The cleanup logic in finally will be ignored. Call .finally on the promise instead:

-toast.promise(EnableBrain(), {
-  loading: 'Enabling autolabeling...',
-  success: 'Autolabeling enabled successfully',
-  error: 'Failed to enable autolabeling',
-  finally: async () => {
-    await refetchBrainState();
-  },
-});
+toast.promise(
+  EnableBrain().finally(refetchBrainState),
+  {
+    loading: 'Enabling autolabeling...',
+    success: 'Autolabeling enabled successfully',
+    error: 'Failed to enable autolabeling',
+  },
+);

Repeat for the Disable branch below.

🧹 Nitpick comments (7)
apps/mail/config/navigation.ts (1)

175-179: Missing id field for new nav item

Every other primary nav item owns an id (e.g. labels, inbox). Down-stream components occasionally rely on that identifier for selection logic. Add one now to stay consistent.

 {
-  title: 'navigation.settings.categories',
+  id: 'categories',
+  title: 'navigation.settings.categories',
   url: '/settings/categories',
   icon: Tabs,
 },
apps/mail/app/(routes)/settings/categories/page.tsx (3)

68-77: Inefficient merge – leverage existing hook

useCategorySettings already implements merge + sort.
Replacing this manual useEffect keeps behaviour in one place and reduces cognitive load.

-const [categories, setCategories] = useState<CategorySetting[]>([]);
-...
-useEffect(() => { ... });
+const { categories, setCategories } = useCategorySettings(); // expose setter in hook

80-83: Typing: avoid any

value is typed as any which defeats type-safety.

-const handleFieldChange = (id: string, field: keyof CategorySetting, value: any) => {
+const handleFieldChange = <K extends keyof CategorySetting>(
+  id: string,
+  field: K,
+  value: CategorySetting[K],
+) => {

110-118: Hard-coded strings bypass i18n

Titles/labels should use the locale JSON keys to stay translatable (SettingsCard title, button text, validation toast).

apps/mail/hooks/use-categories.ts (1)

80-84: Multiple defaults not handled / re-computing constant data

  1. If two categories have isDefault: true, find returns the first and silently keeps the second – worth asserting or warning.
  2. defaultCategories is recreated on every render; hoist it outside the hook (file-scope) to avoid needless allocations.
apps/mail/components/mail/mail.tsx (2)

825-875: Heavy switch-statement & translation helper need tightening

  1. The big switch duplicates a lot of boiler-plate. A static map reduces noise and eases future maintenance.
  2. cat.id.toLowerCase().replace(' ', '') only removes the first space; use /\s+/g to handle any whitespace.

Example refactor:

const ICON_MAP: Record<string, JSX.Element> = {
  Important: <Lightning  />,
  'All Mail': <Mail  />,
  Personal: <User  />,
  Promotions: <Tag  />,
  Updates: <Bell  />,
  Unread: <ScanEye  />,
};

const categories = categorySettings.map((cat) => ({
  ...cat,
  name: cat.name || t(`common.mailCategories.${cat.id.toLowerCase().replace(/\s+/g, '')}` as any),
  icon: ICON_MAP[cat.id],
}));

This also gracefully leaves icon undefined for unknown categories without casting to any.


909-912: Redundant second default lookup

useDefaultCategoryId() is already executed in MailLayout. Re-computing inside every CategorySelect mount adds extra work. Consider lifting the hook up and passing the defaultCategoryId as a prop to avoid duplicate memoisation.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 31229e8 and c971bce.

📒 Files selected for processing (7)
  • apps/mail/app/(routes)/settings/categories/page.tsx (1 hunks)
  • apps/mail/app/routes.ts (1 hunks)
  • apps/mail/components/mail/mail.tsx (6 hunks)
  • apps/mail/config/navigation.ts (1 hunks)
  • apps/mail/hooks/use-categories.ts (1 hunks)
  • apps/mail/locales/en.json (1 hunks)
  • apps/server/src/lib/schemas.ts (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
apps/mail/config/navigation.ts (1)
apps/mail/components/icons/icons.tsx (1)
  • Tabs (594-611)
apps/mail/hooks/use-categories.ts (2)
apps/mail/hooks/use-settings.ts (1)
  • useSettings (5-17)
apps/server/src/lib/utils.ts (1)
  • c (13-23)
🔇 Additional comments (2)
apps/mail/app/routes.ts (1)

56-57: Route added successfully – verify auth guard

The new /settings/categories route is wired correctly.
Double-check that the settings layout already protects children behind the same authentication guard as other settings pages so this one isn’t exposed anonymously.

apps/mail/locales/en.json (1)

358-359: Nice – translation key present

navigation.settings.categories added and matches the nav config.

@retrogtx retrogtx marked this pull request as ready for review June 18, 2025 04:12
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
apps/mail/hooks/use-categories.ts (2)

6-12: id union blocks future custom categories
Previous review still applies – constraining id to six literals prevents user-defined categories that the settings UI implicitly allows.
Change to string (or a branded string) to stay forward-compatible.


24-38: Custom (non-default) categories are silently dropped
The merge only iterates over defaultCategories, so a user-added category that isn’t in the defaults disappears at runtime.
See prior suggestion to include overrides.filter(o => !defaultCategories.some(d => d.id === o.id)).

🧹 Nitpick comments (3)
apps/server/src/lib/prompts.ts (1)

264-274: Consider deduplicating the category-mapping block

The Gmail and Outlook prompts now embed identical (but soon to diverge) XML for default categories.
Extract a single TS constant (e.g., DEFAULT_CATEGORY_MAPPINGS_XML) and interpolate it into each prompt to avoid manual drift when the list is updated or localized.

apps/mail/app/(routes)/settings/categories/page.tsx (2)

120-128: Switch can still leave zero defaults in component state

Turning off the only default sets every category’s isDefault to false, leaving an interim state that fails validation at save time.
While the toast prevents persistence, consider disallowing the toggle-off when it is the sole default, to avoid user confusion.

onCheckedChange={(val) => {
-  const newCats = categories.map((c) => ({
-    ...c,
-    isDefault: c.id === cat.id ? val : false,
-  }));
+  if (!val && cat.isDefault && categories.filter(c => c.isDefault).length === 1) {
+    // Ignore attempt to unset the last default
+    return;
+  }
+  const newCats = categories.map((c) => ({
+    ...c,
+    isDefault: c.id === cat.id ? val : false,
+  }));
   setCategories(newCats);
}}

229-238: Number() accepts hex & scientific notation – prefer parseInt

When parsing the order value, Number('0x10')16 and Number('1e3')1000, which are unlikely to be intended.
parseInt(val, 10) avoids these surprises.

-const parsed = val === '' ? undefined : Number(val);
+const parsed = val === '' ? undefined : parseInt(val, 10);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c971bce and e5815a8.

📒 Files selected for processing (7)
  • apps/mail/app/(routes)/settings/categories/page.tsx (1 hunks)
  • apps/mail/components/mail/mail.tsx (7 hunks)
  • apps/mail/hooks/use-categories.ts (1 hunks)
  • apps/server/src/lib/prompts.ts (2 hunks)
  • apps/server/src/lib/schemas.ts (2 hunks)
  • apps/server/src/trpc/index.ts (1 hunks)
  • apps/server/src/trpc/routes/categories.ts (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • apps/server/src/trpc/index.ts
  • apps/server/src/trpc/routes/categories.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/mail/components/mail/mail.tsx
  • apps/server/src/lib/schemas.ts

Copy link
Collaborator

@MrgSub MrgSub left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
apps/mail/app/(routes)/settings/shortcuts/page.tsx (1)

74-80: Untranslated hard-coded “Show ” prefix + potential undefined access

  1. label = \Show ${cat.name}`bakes English into the UI. Use i18n, e.g. label = t('pages.settings.shortcuts.showLabel', { name: cat.name })`.

  2. The guard categorySettings.length is fine only if the hook always returns an array.
    If it can be undefined during loading, categorySettings.length will throw.
    Either default the hook to [] or change the check to categorySettings?.length.

apps/mail/lib/hotkeys/mail-list-hotkeys.tsx (1)

222-228: Duplicate mapping logic – extract to a shared utility

The same hard-coded index mapping now lives here and in settings/shortcuts/page.tsx. Divergence is likely over time. Export one shared helper (e.g. getCategoryIdForAction(action: 'showImportant' | …)) from use-categories.ts and import it in both places.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5c6e293 and c5ddf1c.

📒 Files selected for processing (2)
  • apps/mail/app/(routes)/settings/shortcuts/page.tsx (3 hunks)
  • apps/mail/lib/hotkeys/mail-list-hotkeys.tsx (2 hunks)

Comment on lines +202 to +209
const switchCategoryByIndex = useCallback(
(idx: number) => {
const cat = categories[idx];
if (!cat) return;
switchMailListCategory(cat.id);
},
[categories, switchMailListCategory],
);
Copy link
Contributor

@coderabbitai coderabbitai bot Jun 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Index-based category lookup suffers from the same reordering problem

switchCategoryByIndex(idx) relies on categories[idx]. Once categories are reordered by the user, hot-keys will target the wrong category.

Reuse a stable id mapping instead:

const actionId: Record<number, string> = {
  ...
};

const cat = categories.find(c => c.id === actionId[idx]);

or expose a helper that resolves showImportant → category.id directly to keep the logic in one place and avoid duplication with the shortcuts page.

🤖 Prompt for AI Agents
In apps/mail/lib/hotkeys/mail-list-hotkeys.tsx around lines 202 to 209, the
function switchCategoryByIndex uses the index to access categories, which breaks
when categories are reordered. To fix this, replace the index-based lookup with
a stable mapping from index to category ID, then find the category by ID in the
categories array. Alternatively, create or reuse a helper function that maps
shortcut actions like 'showImportant' directly to category IDs to centralize and
avoid duplicating this logic.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looked into this, creating a hardcoded stable mapping does not make sense, since user will update it

also, hot-keys will target the wrong category. does not take place.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

I need to better understand how the category system works to verify this behavior. Let me examine the implementation details.


🏁 Script executed:

#!/bin/bash
# Let's examine the category schemas and default categories
echo "=== Category Schema and Default Categories ==="
cat apps/server/src/lib/schemas.ts | grep -A 30 "defaultMailCategories"

echo -e "\n=== Category Settings Hook ==="
cat apps/mail/hooks/use-categories.ts

echo -e "\n=== How Categories are used in the mail component ==="
cat apps/mail/components/mail/mail.tsx | grep -A 20 -B 5 "Categories"

Length of output: 6146


@retrogtx Thank you for the clarification! You're right to dispute my analysis. Looking at the code, I can see the theoretical issue where reordering categories by changing their order property would shift array indices and break the hardcoded hotkey mappings. However, you have the actual implementation context that I'm missing.

Could you help me understand how the category reordering actually works in practice? For instance:

  1. Are there UI constraints that prevent users from reordering the specific default categories that the hotkeys target?
  2. Does the reordering mechanism maintain some stable relationship between hotkeys and their intended categories?
  3. Or is the reordering feature limited to certain types of categories while keeping the default ones fixed?

I'd like to understand the real-world behavior so I can provide more accurate feedback in the future.


✏️ Learnings added
Learnt from: retrogtx
PR: Mail-0/Zero#1328
File: apps/mail/lib/hotkeys/mail-list-hotkeys.tsx:202-209
Timestamp: 2025-06-18T17:26:50.908Z
Learning: In apps/mail/lib/hotkeys/mail-list-hotkeys.tsx, the switchCategoryByIndex function using hardcoded indices for category hotkeys does not break when users reorder categories, contrary to the theoretical index-shifting issue. The actual implementation has constraints or mechanisms that prevent hotkey targeting issues.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants